/**
 * Copyright (C) 2014 serv (liuyuhua69@gmail.com)
 *
 * Licensed under the Apache License, Version 2.0 (the "License"); you may not
 * use this file except in compliance with the License. You may obtain a copy of
 * the License at
 *
 * http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
 * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
 * License for the specific language governing permissions and limitations under
 * the License.
 */
package com.gitlab.bootren.data.core.jpa.impl;

import com.gitlab.bootren.data.core.exception.DataException;
import com.gitlab.bootren.data.core.jpa.PlatformJpaRepository;
import com.gitlab.bootren.data.core.query.ICondition;
import com.gitlab.bootren.data.core.jpa.cmd.Command;
import org.hibernate.SQLQuery;
import org.hibernate.transform.Transformers;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.PageImpl;
import org.springframework.data.domain.Pageable;
import org.springframework.data.domain.Sort;
import org.springframework.data.jpa.repository.query.QueryUtils;
import org.springframework.data.jpa.repository.support.JpaEntityInformation;
import org.springframework.data.jpa.repository.support.JpaEntityInformationSupport;
import org.springframework.data.jpa.repository.support.SimpleJpaRepository;

import javax.persistence.*;
import java.io.Serializable;
import java.lang.reflect.Field;
import java.util.Collections;
import java.util.List;
import java.util.Map;

import static org.apache.commons.lang3.StringUtils.*;

/**
 * Created by serv on 14-5-29.
 */
public class PlatformRepositoryImpl<T,ID extends Serializable> extends SimpleJpaRepository<T, ID> implements PlatformJpaRepository<T,ID> {

    private final EntityManager entityManager;
    private final JpaEntityInformation<T, ?> entityInformation;

    public PlatformRepositoryImpl(Class<T> domainClass, EntityManager entityManager) {
        super(domainClass, entityManager);
        this.entityManager = entityManager;
        this.entityInformation = JpaEntityInformationSupport.getMetadata(domainClass, entityManager);
    }

    @Override
    public T findOne(String condition, Object... values) {
        if (isEmpty(condition)) {
            throw new NullPointerException("条件不能为空!");
        }
        try {
            return (T) createQuery(condition, values).getSingleResult();
        } catch (NoResultException e) {
            return null;
        }

    }

    public List<T> findAll(ICondition condition, Sort sort) {
        Query query = createQuery(condition.toQl("x"), sort, null);
        ParamUtils.setParamToQuery(query, condition);
        return query.getResultList();
    }

    public T findOne(ICondition condition) {
        Query query = createQuery(condition.toQl("x"), null, null);
        query.setMaxResults(1);
        ParamUtils.setParamToQuery(query, condition);
        try {
            return (T) query.getSingleResult();
        } catch (javax.persistence.NoResultException e) {
            return null;
        }

    }

    public Page<T> findAll(ICondition condition, Pageable pageable) {

        if (pageable == null) {
            Sort sort = null;
            return new PageImpl<T>((List<T>) findAll(condition, sort));
        }

        Long total = count(condition);
        String conditionQl = condition.toQl("x");
        Query query = createQuery(conditionQl, pageable.getSort(), null);
        query.setFirstResult(pageable.getOffset());
        query.setMaxResults(pageable.getPageSize());
        ParamUtils.setParamToQuery(query, condition);

        List<T> content = total > pageable.getOffset() ? query.getResultList() : Collections.<T>emptyList();

        return new PageImpl<T>(content, pageable, total);

    }

    public long count(ICondition condition) {
        String conditionQl = condition.toQl("x");
        TypedQuery<Long> query = createCountQuery(conditionQl, null);
        ParamUtils.setParamToQuery(query, condition);
        return QueryUtils.executeCountQuery(query);
    }

    public List<T> findAll(ICondition condition) {
        return this.findAll(condition, (Sort) null);
    }

    @Override
    public List<T> findAll(String condition, Object... objects) {
        return createQuery(condition, objects).getResultList();
    }

    @Override
    public List<T> findAll(String condition, Sort sort, Object... objects) {
        return createQuery(condition, sort, objects).getResultList();
    }

    @Override
    public Page<T> findAll(String condition, Pageable pageable, Object... objects) {

        if (pageable == null) {
            return new PageImpl<T>((List<T>) findAll(condition, objects));
        }

        Long total = count(condition, objects);

        Query query = createQuery(condition, pageable.getSort(), objects);
        query.setFirstResult(pageable.getOffset());
        query.setMaxResults(pageable.getPageSize());

        List<T> content = total > pageable.getOffset() ? query.getResultList() : Collections.<T>emptyList();

        return new PageImpl<T>(content, pageable, total);
    }

    @Override
    public Page<T> findAll(Pageable pageable) {
        return this.findAll("", pageable);
    }

    @Override
    public List<T> findAll(Iterable<ID> ids) {
        return this.findAll("x." + getIdName() + " in ?1", ids);
    }

    @Override
    public T findOne(ID id) {
        return this.findOne("x." + getIdName() + " = ?1", id);
    }

    @Override
    public List<T> findAll(Sort sort) {
        return createQuery(null, sort, null).getResultList();
    }

    @Override
    public List<T> findAll() {
        return createQuery(null, null).getResultList();
    }

    @Override
    public long count() {
        return QueryUtils.executeCountQuery(createCountQuery(null, null));
    }

    @Override
    public long count(String condition, Object... objects) {
        return QueryUtils.executeCountQuery(createCountQuery(condition, objects));
    }

    @Override
    public void deleteByIds(Iterable<ID> ids) {
        List<T> tlist = super.findAll(ids);
        super.deleteInBatch(tlist);
    }

    @Override
    public Class<T> getEntityClass() {
        return entityInformation.getJavaType();
    }

    public String getIdName() {
        Class<?> entityClass = getEntityClass();
        do {
            Field[] fields = entityClass.getDeclaredFields();
            for (Field field : fields) {
                if (field.getAnnotation(Id.class) != null) {
                    return field.getName();
                }
            }
            entityClass = entityClass.getSuperclass();
        } while (entityClass != Object.class);
        throw new DataException(DataException.noID, "未设置主键");
    }

    @Override
    public <S> S executeCommand(Command command) {
        return (S) command.execute(entityManager);
    }

    @Override
    public List<Map<String, Object>> nativeQuery4Map(String sql) {
        Query nativeQuery = entityManager.createNativeQuery(sql);
        nativeQuery.unwrap(SQLQuery.class).setResultTransformer(Transformers.ALIAS_TO_ENTITY_MAP);
        return nativeQuery.getResultList();
    }

    @Override
    public Page<Map> nativeQuery4Map(String sql, Pageable pageable) {
        Query nativeQuery = entityManager.createNativeQuery(sql);
        nativeQuery.unwrap(SQLQuery.class).setResultTransformer(Transformers.ALIAS_TO_ENTITY_MAP);
        nativeQuery.setFirstResult(pageable.getOffset());
        nativeQuery.setMaxResults(pageable.getPageSize());

        Query countNativeQuery = entityManager.createNativeQuery("select count(*) from (" + sql + ") a");
        long total = Long.valueOf(String.valueOf(countNativeQuery.getSingleResult()));

        return new PageImpl<Map>(nativeQuery.getResultList(), pageable, total);
    }

    @Override
    public Object nativeQuery4Object(String sql) {
        Query nativeQuery = entityManager.createNativeQuery(sql);
        List results = nativeQuery.getResultList();
        if (results == null || results.isEmpty()) {
            return null;
        }
        try {
            if (results.size() > 1) {
                throw new RuntimeException("结果应只有一个，但发现多个结果！");
            }
            return results.get(0);
        } catch (Exception e) {
            return null;
        }
    }

    private TypedQuery createCountQuery(String condition, Object[] objects) {

        JpqlQueryHolder queryHolder = new JpqlQueryHolder(condition, objects);

        return queryHolder.createCountQuery();
    }

    /**
     * 声明entityClass的查询
     */
    private Query createQuery(String condition, Sort sort, Object[] objects) {

        JpqlQueryHolder queryHolder = new JpqlQueryHolder(condition, sort, objects);

        return queryHolder.createQuery();
    }

    /**
     * 声明entityClass的查询
     */
    private Query createQuery(String condition, Object[] objects) {
        return createQuery(condition, null, objects);
    }

    private class JpqlQueryHolder {

        //别名
        private final static String ALIAS = "x";

        //QUERY ALL
        private final static String FIND_ALL_QUERY_STRING = "from %s " + ALIAS;

        //传入的condition 排除列表
        private final String[] IGNORE_CONSTAINS_CHARSEQUENCE = {"where", "WHERE", "from", "FROM"};

        private String condition = null;
        private Sort sort;
        private Object[] objects;

        private JpqlQueryHolder(String condition, Sort sort, Object[] objects) {
            this(condition, objects);
            this.sort = sort;
        }

        private JpqlQueryHolder(String condition, Object[] objects) {

            if (startsWithAny(condition, IGNORE_CONSTAINS_CHARSEQUENCE)) {
                throw new DataException(DataException.noPermission, "查询条件中只能包含WHERE条件表达式!");
            }
            this.condition = trimToNull(condition);
            this.objects = objects;
        }

        private Query createQuery() {
            StringBuilder sb = new StringBuilder();
            // select x from table
            sb.append(QueryUtils.getQueryString(FIND_ALL_QUERY_STRING, entityInformation.getEntityName()))
                    //where
                    .append(applyCondition());

            Query query = entityManager.createQuery(QueryUtils.applySorting(sb.toString(), sort, ALIAS));
            applyQueryParameter(query);
            return query;
        }

        private TypedQuery<Long> createCountQuery() {
            String ql = String.format(QueryUtils.COUNT_QUERY_STRING, ALIAS, "%s");
            ql = QueryUtils.getQueryString(ql, entityInformation.getEntityName());
            ql += applyCondition();

            TypedQuery<Long> query = entityManager.createQuery(ql, Long.class);
            applyQueryParameter(query);
            return query;
        }

        private String applyCondition() {

            return isEmpty(condition) ? "" : " where " + condition;
        }

        private void applyQueryParameter(Query query) {
            if (objects != null) {
                int i = 0;
                for (Object value : objects) {
                    i++;
                    query.setParameter(i, value);
                }
            }

        }

    }

}
